package ran.friend.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import ran.constants.Constants;
import ran.constants.HttpConstants;
import ran.constants.RedisCacheConstants;
import ran.constants.UserIdentity;
import ran.friend.domain.user.dto.UserCodeDTO;
import ran.friend.domain.user.dto.UserEditDTO;
import ran.friend.domain.user.entity.User;
import ran.entity.LoginUser;
import ran.entity.Result;
import ran.enums.ResultCode;
import ran.enums.UserStatus;
import ran.exception.ServiceException;
import ran.friend.domain.user.vo.UserVO;
import ran.friend.manager.UserRedisCacheManager;
import ran.friend.service.user.IUserService;
import ran.mail.MailService;
import ran.friend.mapper.user.UserMapper;
import ran.redis.service.RedisService;
import ran.service.TokenService;
import ran.utils.ThreadLocalUtil;
import ran.vo.LoginUserVO;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: ran
 * Date: 2025-11-03
 * Time: 10:50
 */
@Service
@Slf4j
public class UserServiceImpl implements IUserService {
    @Autowired
    private MailService mailService;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisService redisService;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private UserRedisCacheManager userRedisCacheManager;


    @Value("${jwt.secret}")
    private String secret;
    @Value("${mail.code-expiration:5}")
    private Long mailCodeExpiration;
    @Value("${mail.send-limit:3}")
    private Integer sendLimit;
    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Override
    public boolean sendCode(UserCodeDTO codeDTO){
        if (!checkMail(codeDTO.getMail())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        String mail = codeDTO.getMail();
        // 1.查询验证码获取的时间,限制一分钟内一次获取, 使用Redis, 选取String类型, key-> m:c: 邮箱  value-> code
        String mailKey = getMailKey(mail);
        Long expireTime = redisService.getExpireTime(mailKey, TimeUnit.SECONDS);
        if (expireTime != null && mailCodeExpiration * 60 - expireTime < 60) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT_CODE);
        }
        // 2.限制当天总获取次数 50 , 使用Redis, 选取String类型, key-> c:t: value-> Long类型
        String timeCountKey = getTimeCountKey(mail);
        Long sendTimeCount = redisService.getCacheObject(timeCountKey, Long.class);
        if (sendTimeCount != null && sendTimeCount >= sendLimit) {
            throw new ServiceException(ResultCode.FAILED_CODE_LIMIT);
        }
        String code = RandomUtil.randomNumbers(6);
        // --------------------
        code = "123456";
        // --------------------

        // 发送邮件验证码
        boolean sent = mailService.sendMail(mail, code);
        if (!sent) {
            throw new ServiceException(ResultCode.FAILED_MAIL_SEND);
        }
        // 存储验证码
        redisService.setCacheObject(mailKey,code,mailCodeExpiration,TimeUnit.MINUTES);
        // 获取次数+1
        redisService.increment(timeCountKey);
        // 设置sendTimeCount过期时间, 前提是:(当天第一次发起验证码)
        if (sendTimeCount == null) {
            // 当天第一次发起验证码, 设置过期时间, 要求是当天24点过期
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(timeCountKey,seconds,TimeUnit.SECONDS);
        }
        return true;
    }

    @Override
    public String codeLogin(String mail, String code) {
        // -- 先比对验证码
        checkCode(mail, code);
        // -- 判断新老用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getEmail, mail));
        // 1. 新用户注册
        if (user == null) {
            // 插入到MySQL数据库中
            user = new User();
            user.setEmail(mail);
            user.setStatus(UserStatus.Normal.getValue());
            userMapper.insert(user);
        }
        // 2. 老用户和新用户都要创建token
        return tokenService.createToken(user.getUserId(),secret, UserIdentity.ORDINARY.getValue(),user.getNickName(),user.getHeadImage());
    }

    @Override
    public boolean logout(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
//        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
//            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
//        }
        return tokenService.deleteLoginUser(ThreadLocalUtil.get(Constants.USER_KEY,String.class));
    }

    // 获取用户信息, 只获取头像和昵称
    @Override
    public Result<LoginUserVO> info(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
//        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
//            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
//        }
        LoginUser loginUser = tokenService.getLoginUser(ThreadLocalUtil.get(Constants.USER_KEY,String.class));
        if (loginUser == null) {
            return Result.fail();
        }
        log.info("获取登录用户信息, loginUser:{}",loginUser);
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        if (StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            loginUserVO.setHeadImage(downloadUrl+loginUser.getHeadImage());

        }
        return Result.ok(loginUserVO);
    }

    // 获取用户详情信息
    @Override
    public UserVO detail() {
        // 先去Redis中查询, 没有的话去数据库,同步给缓存
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userRedisCacheManager.getUserDetail(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl+userVO.getHeadImage());
        }
        return userVO;
    }

    @Override
    public int edit(UserEditDTO userEditDTO) {
        // 修改Redis的user信息, loginUser信息, MySQL的用户信息
        User user = checkAndGetUser();
        user.setNickName(userEditDTO.getNickName());
        user.setSex(userEditDTO.getSex());
        user.setPhone(userEditDTO.getPhone());
        user.setEmail(userEditDTO.getEmail());
        user.setWechat(userEditDTO.getWechat());
        user.setSchoolName(userEditDTO.getSchoolName());
        user.setMajorName(userEditDTO.getMajorName());
        user.setIntroduce(userEditDTO.getIntroduce());
        return updateCache(user);
    }

    @Override
    public int updateHeadImage(String headImage) {
        User user = checkAndGetUser();
        user.setHeadImage(headImage);
        // 更新登录用户中LoginUser的信息
        return updateCache(user);
    }

    private int updateCache(User user) {
        // 更新登录用户中LoginUser的信息
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY,String.class));
        // 更新user详细信息
        userRedisCacheManager.refreshCache(user);
        // 更新MySQL
        return userMapper.updateById(user);
    }

    private User checkAndGetUser() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        return user;
    }

    private void checkCode(String mail, String code) {
        // 1. 通过 mailKey 去 Redis 获取验证码
        String mailKey = getMailKey(mail);
        String cacheCode = redisService.getCacheObject(mailKey, String.class);
        // 2. 判断 cacheCode
        // 已经过期
        if (StrUtil.isEmpty(cacheCode)) {
            throw new ServiceException(ResultCode.FAILED_EXPIRED_CODE);
        }
        // 验证码错误
        if (!cacheCode.equals(code)) {
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        // 验证码正确, 删除Redis中的验证码
        redisService.deleteObject(mailKey);
    }

    private String getTimeCountKey(String mail) {
        return RedisCacheConstants.SEND_TIME_KEY + mail;
    }

    private String getMailKey(String mail) {
        return RedisCacheConstants.MAIL_CODE_KEY + mail;
    }

    private boolean checkMail(String phone) {
        Pattern pattern = Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
        Matcher matcher = pattern.matcher(phone);
        return matcher.matches();
    }
}
